/*******************************************************************************
 * Copyright (c) 2013 University of Luxembourg and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     @author Qin ZHANG ("Jackin" as a Global Communication ID)
 ******************************************************************************/

package lu.uni.geko.ext.ram;

import java.util.List;

import lu.uni.geko.common.AbstractModelTransformer;
import lu.uni.geko.common.JoinPoint;
import lu.uni.geko.joinpointdetection.MainJoinpointDetector;
import lu.uni.geko.mapping.Pc2AvMapResolver;
import lu.uni.geko.resources.MainResourceLoader;
import lu.uni.geko.util.datastructures.N2NMap;
import lu.uni.geko.weaver.Weaver;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;

import ram.Aspect;
import ram.AttributeMapping;
import ram.ClassifierMapping;
import ram.Instantiation;
import ram.OperationMapping;
import ram.ParameterMapping;
import ram.RamFactory;
import ram.RamPackage;
import ram_av.Advice;
import ram_av.Ram_avFactory;
import ram_pc.Pointcut;
import ram_pc.Ram_pcFactory;
import Pc2AvMapping.Mapping;
import Pc2AvMapping.MappingEntry;
import Pc2AvMapping.Pc2AvMappingFactory;

/**
 * @author Jackin
 * 
 *         This is an extended weaver for TouchRAM.
 */
public final class WeaverExt extends AbstractModelTransformer<URI>
{
	public WeaverExt(final URI baseMURI)
	{
		super(baseMURI);
	}

	/**
	 * Extension for TouchRAM model weaving. The weaving process is recursively
	 * (depth-first algorithm).
	 * 
	 * @param baseMURI
	 *          the URI of the base model (RAM aspect)
	 * @return the URI of the woven model (Jackin: original base model)
	 */
	public final URI weave(URI baseMURI)
	{
		/*
		 * If the base aspect is still depending on other aspects, go recursively to
		 * the leaf aspect which doesn't contain any instantiation entry. Otherwise,
		 * weave current aspect with all its dependent aspects fetched from the
		 * instantiation entries. (Depth-first algorithm)
		 */
		Aspect base = (Aspect) MainResourceLoader.getUniqueContentRoot(baseMURI, "temporary base model");
		EList<Instantiation> instantiations = base.getInstantiations();

		if (instantiations != null)
		{
			for (Instantiation instantiation : instantiations)
			{
				try
				{
					/*
					 * The dependent is an external aspect which the current base aspect
					 * is depending on.
					 */
					
					URI dependentMURI = ( (InternalEObject) instantiation.eGet(instantiation.eClass().getEStructuralFeature(RamPackage.INSTANTIATION__EXTERNAL_ASPECT), false)).eProxyURI();
					Aspect dependent = (Aspect) instantiation.getExternalAspect();
					

					/*
					 * According to RAM, any aspect must have exact one structural view.
					 * If the structural view is null, that means the expected dependent
					 * aspect is not found.
					 */
					if (dependent.getStructuralView() == null)
						throw new RuntimeException("Cannot find the dependent RAM aspect: " + dependent.toString() + ", or the expected RAM aspect doesn't contain structural view!");

					/*
					 * If the dependent aspect is OK, go recursively to weave it with all
					 * its dependent aspects.
					 */
					weave(dependentMURI);

					/**
					 * When finishing weaving the dependent aspect with all its own
					 * dependent aspects, weave this dependent aspect onto the current
					 * base aspect.
					 */

					/*
					 * First backup the current base aspect, since we are going to weave
					 * dependent aspect on it.
					 */
					RamUtil.backup(baseMURI);
					// dependent = (Aspect) instantiation.getExternalAspect();

					/*
					 * Change the element names in the dependent aspect, if the
					 * instantiation type is "depends", according to RAM meta-model. The
					 * instantiation type "extends" is the default GeKo mode.
					 */
					if (instantiation.getType().getLiteral().equalsIgnoreCase(RamConstants.INSTANTIATION_TYPE_DEPENDS))
						RamUtil.instantiate(instantiation);

					/*
					 * Weave current base and dependent aspects by each segment, which
					 * adapts GeKo generic weaving process.
					 */
					weaveBySegment(baseMURI, dependentMURI, instantiation);

					// Detect the joinpoints between base and dependent aspects.
					// MainResourceLoader.saveEObjectAsOnlyContent(dependent,
					// createTempURI(this.wovenMURI, RamConstants.ADVICE_FILE_EXT));
				} catch (Exception ex)
				{
					System.err.println(ex);
				}
			} // end of for
		} // end of if

		return baseMURI;
	}

	private void weaveBySegment(URI baseMURI, URI dependentMURI, Instantiation instantiation)
	{
		/*
		 * Get base and dependent
		 */
		Aspect base = (Aspect) MainResourceLoader.getUniqueContentRoot(baseMURI, "temporary base model");
		Aspect dependent = (Aspect) MainResourceLoader.getUniqueContentRoot(dependentMURI, "temporary base model");
		
		/*
		 * Necessary temporary URIs
		 */
		URI tempPointcutMURI = createTempURI(this.getMURI(), RamConstants.POINTCUT_FILE_EXT);
		URI tempAdviceMURI = createTempURI(this.getMURI(), RamConstants.ADVICE_FILE_EXT);
		URI tempMappingMURI = createTempURI(this.getMURI(), RamConstants.MAPPING_FILE_EXT);

		/*
		 * Temporary model objects
		 */
		Advice tempAdvice = Ram_avFactory.eINSTANCE.createAdvice();
		Pointcut tempPointcut = Ram_pcFactory.eINSTANCE.createPointcut();
		Mapping tempMapping = Pc2AvMappingFactory.eINSTANCE.createMapping();

		/**
		 * Re-organize the structural view
		 */

		/*
		 * create a temporary advice structural view
		 */
		ram.StructuralView tempAdviceStructuralView = (ram.StructuralView) dependent.getStructuralView();
		tempAdvice.getChildren().add(tempAdviceStructuralView);

		/*
		 * Create a temporary pointcut structural view and relevant pc2avmapping.
		 * 
		 * Note that we cheat the canonical class name of each pointcut element
		 * for GeKo to recognize it later.
		 * since these elements are under ram meta-model, not pointcut meta-model.
		 * 
		 * @see eObject.eClass().getInstanceClass().getCanonicalName();
		 */
		ram.StructuralView tempPointcutStructuralView = RamFactory.eINSTANCE.createStructuralView();
		tempPointcut.getChildren().add(tempPointcutStructuralView);

		addMappingEntry(tempMapping, tempPointcutStructuralView, tempAdviceStructuralView);

		for (ClassifierMapping classifierMapping : instantiation.getMappings())
		{
			/*
			 * Note that the to-element is located in the base model, and the
			 * from-element is in the dependent model.
			 */
			ram.Class pcClass = (ram.Class) classifierMapping.getToElement();
			ram.Class avClass = (ram.Class) classifierMapping.getFromElement();
			addMappingEntry(tempMapping, pcClass, avClass);
			tempPointcutStructuralView.getClasses().add(pcClass);

			// instantiate attribute mapping if applicable
			if (classifierMapping.getAttributeMappings() != null)
				for (AttributeMapping attributeMapping : classifierMapping.getAttributeMappings())
				{
					ram.Attribute pcAttribute = (ram.Attribute) attributeMapping.getToElement();
					ram.Attribute avAttribute = (ram.Attribute) attributeMapping.getFromElement();
					addMappingEntry(tempMapping, pcAttribute, avAttribute);
					pcClass.getAttributes().add(pcAttribute);
				}

			// instantiate operation mapping if applicable
			if (classifierMapping.getOperationMappings() != null)
				for (OperationMapping operationMapping : classifierMapping.getOperationMappings())
				{
					ram.Operation pcOperation = (ram.Operation) operationMapping.getToElement();
					ram.Operation avOperation = (ram.Operation) operationMapping.getFromElement();
					addMappingEntry(tempMapping, pcOperation, avOperation);
					pcClass.getOperations().add(pcOperation);

					// instantiate parameter mapping if applicable
					if (operationMapping.getParameterMappings() != null)
						for (ParameterMapping parameterMapping : operationMapping.getParameterMappings())
						{
							ram.Parameter pcParameter = (ram.Parameter) parameterMapping.getToElement();
							ram.Parameter avParameter = (ram.Parameter) parameterMapping.getFromElement();
							addMappingEntry(tempMapping, pcParameter, avParameter);
							pcOperation.getParameters().add(pcParameter);
						}
				}
		} // end of for(ClassifierMapping)
		
		/**
		 * Weave with base, pointcut, advice and pc2avmapping, adapted for GeKo.
		 */
		
		/*
		 * Save URIs as follows:
		 * 		base: baseMURI
		 * 		pointcut: tempPointcutMURI
		 * 		advice: tempAdviceMURI
		 * 		mapping: tempMappingMURI
		 * 
		 * The woven model URI will be the same as the base (that's the reason we backup the base model before):
		 * 		woven: baseMURI
		 */
		MainResourceLoader.saveEObjectAsOnlyContent(tempPointcut, tempPointcutMURI);
		MainResourceLoader.saveEObjectAsOnlyContent(tempAdvice, tempAdviceMURI);
		MainResourceLoader.saveEObjectAsOnlyContent(tempMapping, tempMappingMURI);
		
		/*
		 * Reuse GeKo weaver.
		 */		
		N2NMap<EObject, EObject> pc2AvN2NMap = (new Pc2AvMapResolver(tempPointcutMURI, tempAdviceMURI, tempMappingMURI)).transform();
    if (pc2AvN2NMap == null) 
    {
       return;
    }
    
		Weaver gekoWeaver = new Weaver(baseMURI, tempAdviceMURI, pc2AvN2NMap);
    URI wovenMURI = baseMURI;
    List<JoinPoint> joinPoints = MainJoinpointDetector.detectJoinpoints(tempPointcutMURI, wovenMURI);
    gekoWeaver.setJoinPoints(joinPoints);
    gekoWeaver.transform();	
	}

	// one-to-one mapping entry
	private static void addMappingEntry(Mapping mapping, EObject sourceObject, EObject targetObject)
	{
		MappingEntry entry = Pc2AvMappingFactory.eINSTANCE.createMappingEntry();
		entry.getSource().add(sourceObject);
		entry.getTarget().add(targetObject);
		mapping.getEntries().add(entry);
	}

	private static URI createTempURI(final URI uri, final String newFileExt)
	{
		String fileExt = uri.fileExtension();
		if (fileExt != null)
		{
			URI uriWithoutFileExt = uri.trimFileExtension();
			String resultFileName = uriWithoutFileExt.lastSegment() + "_temp";
			return uriWithoutFileExt.trimSegments(1).appendSegment(resultFileName).appendFileExtension(newFileExt);
		} else
		{
			throw new RuntimeException("The URI '" + uri + "' has no file extension!");
		}
	}

	@Override
	public URI transform()
	{
		return null;
	}

}
